﻿using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace NB_Controller_Test
{
    /// <summary>
    /// https://www.cnblogs.com/uu102/archive/2013/02/16/2913410.html
    /// </summary>
    public class HttpProcessor
    {
        public TcpClient tcpClient;
        public HttpServer httpServer;

        private Stream inputStream;
        public StreamWriter outputStream;

        public String http_method;
        public String http_url;
        public String http_protocol_versionstring;
        public Hashtable httpHeaders = new Hashtable();

        public BinFileManager fileManager = new BinFileManager();

        private static int MAX_POST_SIZE = 10 * 1024 * 1024; // 10MB
        private const int BUF_SIZE = 4096;

        private delegate void SwitchProgress(string strIP, UInt16 usProgress);

        public HttpProcessor(TcpClient client, HttpServer server)
        {
            this.tcpClient = client;
            this.httpServer = server;
        }

        private string StreamReadLine(Stream inputStream)
        {
            int next_char;
            string data = "";
            while (true)
            {
                next_char = inputStream.ReadByte();
                if (next_char == '\n') { break; }
                if (next_char == '\r') { continue; }
                if (next_char == -1) { Thread.Sleep(1); continue; };
                data += Convert.ToChar(next_char);
            }
            return data;
        }

        public void Process()
        {
            // we can't use a StreamReader for input, because it buffers up extra data on us inside it's
            // "processed" view of the world, and we want the data raw after the headers
            inputStream = new BufferedStream(tcpClient.GetStream());

            // we probably shouldn't be using a streamwriter for all output from handlers either
            outputStream = new StreamWriter(new BufferedStream(tcpClient.GetStream()));
            try
            {
                ParseRequest();
                ReadHeaders();
                if (http_method.Equals("GET"))
                {
                    HandleGetRequest();
                }
                else if (http_method.Equals("POST"))
                {
                    HandlePostRequest();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: " + e.ToString());
                WriteFailure();
            }
            //outputStream.Flush();
            // flush any remaining output
            inputStream = null; 
            outputStream = null;        

            //不关闭貌似下发的数据流无法到达
            CloseClient();
        }

        public void CloseClient()
        {
            if (tcpClient != null)
                tcpClient.Close();
        }

        public void ParseRequest()
        {
            String request = StreamReadLine(inputStream);
            string[] tokens = request.Split(' ');
            if (tokens.Length != 3)
            {
                throw new Exception("invalid http request line");
            }
            http_method = tokens[0].ToUpper();
            http_url = tokens[1];
            http_protocol_versionstring = tokens[2];

            Console.WriteLine("starting: " + request);
        }

        public void ReadHeaders()
        {
            Console.WriteLine("readHeaders()");
            String line;
            while ((line = StreamReadLine(inputStream)) != null)
            {
                if (line.Equals(""))
                {
                    Console.WriteLine("got headers");
                    return;
                }

                int separator = line.IndexOf(':');
                if (separator == -1)
                {
                    throw new Exception("invalid http header line: " + line);
                }
                String name = line.Substring(0, separator);
                int pos = separator + 1;
                while ((pos < line.Length) && (line[pos] == ' '))
                {
                    pos++; // strip any spaces
                }

                string value = line.Substring(pos, line.Length - pos);
                Console.WriteLine("header: {0}:{1}", name, value);
                httpHeaders[name] = value;
            }
        }

        public void HandleGetRequest()
        {
            httpServer.HandleGetRequest(this);
        }
        
        public void HandlePostRequest()
        {
            // this post data processing just reads everything into a memory stream.
            // this is fine for smallish things, but for large stuff we should really
            // hand an input stream to the request processor. However, the input stream 
            // we hand him needs to let him see the "end of the stream" at this content 
            // length, because otherwise he won't know when he's seen it all! 

            Console.WriteLine("get post data start");
            int content_len = 0;
            MemoryStream ms = new MemoryStream();
            if (this.httpHeaders.ContainsKey("Content-Length"))
            {
                content_len = Convert.ToInt32(this.httpHeaders["Content-Length"]);
                if (content_len > MAX_POST_SIZE)
                {
                    throw new Exception(
                        String.Format("POST Content-Length({0}) too big for this simple server",
                          content_len));
                }
                byte[] buf = new byte[BUF_SIZE];
                int to_read = content_len;
                while (to_read > 0)
                {
                    Console.WriteLine("starting Read, to_read={0}", to_read);

                    int numread = this.inputStream.Read(buf, 0, Math.Min(BUF_SIZE, to_read));
                    Console.WriteLine("read finished, numread={0}", numread);
                    if (numread == 0)
                    {
                        if (to_read == 0)
                        {
                            break;
                        }
                        else
                        {
                            throw new Exception("client disconnected during post");
                        }
                    }
                    to_read -= numread;
                    ms.Write(buf, 0, numread);
                }
                ms.Seek(0, SeekOrigin.Begin);
            }
            Console.WriteLine("get post data end");
            httpServer.HandlePostRequest(this, new StreamReader(ms));

        }

        public void WriteSuccess()
        {
            outputStream.WriteLine("HTTP/1.0 200 OK");
            outputStream.WriteLine("Content-Type: text/html");
            outputStream.WriteLine("Connection: close");
            outputStream.WriteLine("");
        }

        public void WriteFailure()
        {
            outputStream.WriteLine("HTTP/1.0 404 File not found");
            outputStream.WriteLine("Connection: close");
            outputStream.WriteLine("");
        }

        public void WriteHttpResp(int nDatasTotalSize)
        {
            /*
             * HTTP/1.1 200 OK
                Content-Type: application/octet-stream
                Content-Length: 1048944
                Accept-Ranges: bytes
                Server: HFS 2.3k
                Set-Cookie: HFS_SID_=0.586792730726302; path=/; HttpOnly
                ETag: 8388B3AEC76C3EE0DA1E624BD02584B1
                Last-Modified: Mon, 27 May 2019 09:15:37 GMT
                Content-Disposition: attachment; filename="opple.bin";
             * */

            ///*
            outputStream.WriteLine("HTTP/1.0 200 OK");
            outputStream.WriteLine("Content-Type: application/octet-stream");
            outputStream.WriteLine("Content-Length: " + nDatasTotalSize.ToString());
            outputStream.WriteLine("Accept-Ranges: bytes");
            outputStream.WriteLine("Server: HFS 2.3k opple self");
            outputStream.WriteLine("Set-Cookie: HFS_SID_=0.586792730726302; path=/; HttpOnly");
            outputStream.WriteLine("ETag: 8388B3AEC76C3EE0DA1E624BD02584B1");
            outputStream.WriteLine("Last-Modified: Mon, 27 May 2019 09:15:37 GMT");
            outputStream.WriteLine("Content-Disposition: attachment; filename=\"opple.bin\";");
            outputStream.WriteLine("");
            //outputStream.WriteLine("");

            outputStream.Flush();
             //* */            
        }

        public void SendDataToClient(System.Windows.Controls.UserControl controlIn)
        {
            UInt16 usProgress = 0;
            //第一包数据，带上Http的头，剩余数据包只包含有效数据，最后一包结束后主动断开连接
            while(fileManager.uCurPackIndex != fileManager.uTotalPakeSize)
            {
                //Thread.Sleep(15);

                byte[] byDatas = fileManager.GetCurPackData();                

                //考虑失败后重试
                try
                {
                    //Send
                    int nSize = tcpClient.Client.Send(byDatas);
                    //System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " nSize: " + nSize.ToString());
                    if (nSize != byDatas.Length)
                    {
                        //nSize = nSize;
                        ;//
                    }
                    else
                    {
                        //成功发送
                        fileManager.uCurPackIndex++;

                        UInt16 usPro = (UInt16)((float)fileManager.uCurPackIndex / (float)fileManager.uTotalPakeSize * 100.0f);
                        if(usPro == usProgress)
                        {
                            //不更新
                        }
                        else
                        {
                            //更新                            
                            usProgress = usPro;
                            //抛出消息到UI controlIn
                            controlIn.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new SwitchProgress(((MainUIEx)controlIn).SwitchOTADownProgress), tcpClient.Client.RemoteEndPoint.ToString(), usProgress);
                        }
                    }
                }
                catch(Exception e)
                {
                    e = e;
                    //通知设备重新启动OTA流程
                    controlIn.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new SwitchProgress(((MainUIEx)controlIn).SwitchOTADownProgress), tcpClient.Client.RemoteEndPoint.ToString(), (UInt16)(999));
                    return;
                }                
            }
        }
    }

    public abstract class HttpServer
    {
        public int nPort;
        private TcpListener tcpListener;
        private bool bIsActive = true;

        public HttpServer(int port)
        {
            this.nPort = port;
        }

        public void Exit()
        {
            bIsActive = false;

            StopListen();
        }

        public void Listen()
        {
            StopListen();

            //在指定端口监听
            tcpListener = new TcpListener(nPort);
            tcpListener.Start();

            try
            {
                //循环等到连接到来
                while (bIsActive)
                {
                    TcpClient tcpClient = tcpListener.AcceptTcpClient();
                    HttpProcessor processor = new HttpProcessor(tcpClient, this);
                    //开启线程执行连接到来后的处理逻辑
                    Thread thread = new Thread(new ThreadStart(processor.Process));
                    thread.Start();

                    Thread.Sleep(15);
                }
            }
            catch(Exception e)
            {
                ;
            }
            
        }

        public void StopListen()
        {
            if (tcpListener != null)
            {
                tcpListener.Stop();
            }                

            tcpListener = null;
        }

        public abstract void HandleGetRequest(HttpProcessor p);

        public abstract void HandlePostRequest(HttpProcessor p, StreamReader inputData);
    }

    public class HttpServerEx : HttpServer
    {
        private System.Windows.Controls.UserControl control;

        public HttpServerEx(System.Windows.Controls.UserControl controlIn, int port)
            : base(port)
        {
            control = controlIn;
        }

        public override void HandleGetRequest(HttpProcessor httpPro)
        {
            /*
             * HTTP/1.1 200 OK
                Content-Type: application/octet-stream
                Content-Length: 1048944
                Accept-Ranges: bytes
                Server: HFS 2.3k
                Set-Cookie: HFS_SID_=0.586792730726302; path=/; HttpOnly
                ETag: 8388B3AEC76C3EE0DA1E624BD02584B1
                Last-Modified: Mon, 27 May 2019 09:15:37 GMT
                Content-Disposition: attachment; filename="opple.bin";
             * */

            //解析出请求的Bin文件
            string strUrl = httpPro.http_url;
            strUrl = @"hfs" + strUrl;
            httpPro.fileManager.LoadBinFile(strUrl);
            //读取Bin文件大小
            int nDatasSize = httpPro.fileManager.byTotalData.Length;
            //返回特定的格式给设备
            httpPro.WriteHttpResp(nDatasSize);
            //将Bin文件拆包，一包包下发给设备（第一包带上Http协议头，后续报只包含纯净数据；亦可第一包只有Http协议头，后续数据包都是纯净数据）
            //下发数据包时判断是否下发成功，若不成功后续再重复或者等待
            //下发完毕后，主动断开连接给设备
            httpPro.SendDataToClient(control);
        }

        public override void HandlePostRequest(HttpProcessor httpPro, StreamReader inputData)
        {
            Console.WriteLine("POST request: {0}", httpPro.http_url);
            string data = inputData.ReadToEnd();

            httpPro.outputStream.WriteLine("<html><body><h1>test server</h1>");
            httpPro.outputStream.WriteLine("<a href=/test>return</a><p>");
            httpPro.outputStream.WriteLine("postbody: <pre>{0}</pre>", data);
        }
    }

    public class BinFileManager
    {
        public const UInt16 uOnePakeSize = 512;//768
        private byte[] curPackData = null;
        private byte byStartIndex = 0;

        public UInt32 ulTotalDataSize = 0;
        public UInt16 uTotalPakeSize = 0;
        //成功后需要主动加1
        public UInt16 uCurPackIndex = 0;

        public byte[] byTotalData = null;

        public bool LoadBinFile(string strPath)
        {
            if (byTotalData != null)
            {
                return true;
            }
            else
            {
                try
                {
                    byTotalData = File.ReadAllBytes(strPath);
                    CalFileData();
                    return true;
                }
                catch(Exception e)
                {
                    byTotalData = null;
                    return false;
                }                
            }
        }

        public void CalFileData()
        {
            if (byTotalData == null)
                return;
            ulTotalDataSize = (UInt32)byTotalData.Length;
            if (ulTotalDataSize == 0)
            {
                return;
            }

            uTotalPakeSize = (UInt16)((ulTotalDataSize / uOnePakeSize) + 1);
            //重新计算当前包序号
            uCurPackIndex = 0;
        }

        public byte[] GetCurPackData()
        {
            if (ulTotalDataSize == 0)
            {
                return null;
            }

            //当前包大小
            UInt16 curPakeSize = uOnePakeSize;
            if (uCurPackIndex >= uTotalPakeSize - 1)
            {
                //最后一包的大小
                curPakeSize = (UInt16)(ulTotalDataSize - (byStartIndex + uCurPackIndex * uOnePakeSize));
            }

            if (uCurPackIndex >= uTotalPakeSize)
            {
                //防护一下，防止越界
                return null;
            }


            curPackData = new byte[curPakeSize];
            Array.Copy(byTotalData, byStartIndex + uCurPackIndex * uOnePakeSize, curPackData, 0, curPakeSize);
            return curPackData;
        }        
    }
}



